package org.thoughtcrime.securesms.stories.viewer.reply

import android.animation.FloatEvaluator
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.AttributeSet
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.android.material.card.MaterialCardView
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.DecryptableUri
import org.thoughtcrime.securesms.stories.StoryTextPostModel
import kotlin.reflect.KProperty

class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
  context: Context,
  attrs: AttributeSet? = null
) : MaterialCardView(context, attrs), CrossfaderTransition.Crossfadeable {

  companion object {
    val CORNER_RADIUS_START = DimensionUnit.DP.toPixels(12f)
    val CORNER_RADIUS_END = DimensionUnit.DP.toPixels(18f)
    val CORNER_RADIUS_EVALUATOR = FloatEvaluator()
  }

  init {
    inflate(context, R.layout.stories_shared_element_crossfader, this)
  }

  private val sourceView: ImageView = findViewById(R.id.source_image)
  private val sourceBlurView: ImageView = findViewById(R.id.source_image_blur)
  private val targetView: ImageView = findViewById(R.id.target_image)
  private val targetBlurView: ImageView = findViewById(R.id.target_image_blur)

  private var isSourceReady: Boolean by NotifyIfReadyDelegate(false)
  private var isSourceBlurReady: Boolean by NotifyIfReadyDelegate(false)
  private var isTargetReady: Boolean by NotifyIfReadyDelegate(false)
  private var isTargetBlurReady: Boolean by NotifyIfReadyDelegate(false)

  private var latestSource: Any? = null
  private var latestTarget: Any? = null

  var callback: Callback? = null

  fun setSourceView(storyTextPostModel: StoryTextPostModel) {
    if (latestSource == storyTextPostModel) {
      return
    }

    latestSource = storyTextPostModel

    Glide.with(sourceView)
      .load(storyTextPostModel)
      .addListener(
        OnReadyListener {
          isSourceReady = true
        }
      )
      .placeholder(storyTextPostModel.getPlaceholder())
      .dontAnimate()
      .centerCrop()
      .into(sourceView)

    Glide.with(sourceBlurView).clear(sourceBlurView)
    isSourceBlurReady = true
  }

  fun setSourceView(uri: Uri, blur: BlurHash?) {
    if (latestSource == uri) {
      return
    }

    latestSource = uri

    Glide.with(sourceView)
      .load(DecryptableUri(uri))
      .addListener(
        OnReadyListener {
          isSourceReady = true
        }
      )
      .dontAnimate()
      .centerCrop()
      .into(sourceView)

    if (blur == null) {
      Glide.with(sourceBlurView).clear(sourceBlurView)
      isSourceBlurReady = true
    } else {
      Glide.with(sourceBlurView)
        .load(blur)
        .addListener(
          OnReadyListener {
            isSourceBlurReady = true
          }
        )
        .dontAnimate()
        .centerCrop()
        .into(sourceBlurView)
    }
  }

  fun setTargetView(messageRecord: MmsMessageRecord): Boolean {
    val thumbUri = messageRecord.slideDeck.thumbnailSlide?.uri
    val thumbBlur: BlurHash? = messageRecord.slideDeck.thumbnailSlide?.placeholderBlur
    when {
      messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord))
      thumbUri != null -> setTargetView(thumbUri, thumbBlur)
      else -> return false
    }

    return true
  }

  private fun setTargetView(storyTextPostModel: StoryTextPostModel) {
    if (latestTarget == storyTextPostModel) {
      return
    }

    latestTarget = storyTextPostModel

    Glide.with(targetView)
      .load(storyTextPostModel)
      .addListener(
        OnReadyListener {
          isTargetReady = true
        }
      )
      .dontAnimate()
      .placeholder(storyTextPostModel.getPlaceholder())
      .centerCrop()
      .into(targetView)

    Glide.with(targetBlurView).clear(targetBlurView)
    isTargetBlurReady = true
  }

  private fun setTargetView(uri: Uri, blur: BlurHash?) {
    if (latestTarget == uri) {
      return
    }

    latestTarget = uri

    Glide.with(targetView)
      .load(DecryptableUri(uri))
      .addListener(
        OnReadyListener {
          isTargetReady = true
        }
      )
      .dontAnimate()
      .fitCenter()
      .into(targetView)

    if (blur == null) {
      Glide.with(targetBlurView).clear(targetBlurView)
      isTargetBlurReady = true
    } else {
      Glide.with(targetBlurView)
        .load(blur)
        .addListener(
          OnReadyListener {
            isTargetBlurReady = true
          }
        )
        .dontAnimate()
        .centerCrop()
        .into(targetBlurView)
    }
  }

  private fun notifyIfReady() {
    if (isSourceReady && isTargetReady && isSourceBlurReady && isTargetBlurReady) {
      callback?.onReadyToAnimate()
    }
  }

  private class OnReadyListener(private val onReady: () -> Unit) : RequestListener<Drawable> {
    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
      onReady()
      return false
    }

    override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
      onReady()
      return false
    }
  }

  override fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean) {
    if (reverse) {
      sourceView.alpha = progress
      sourceBlurView.alpha = progress
      targetView.alpha = 1f - progress
      targetBlurView.alpha = 1f - progress
      radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START)
    } else {
      sourceView.alpha = 1f - progress
      sourceBlurView.alpha = 1f - progress
      targetView.alpha = progress
      targetBlurView.alpha = progress
      radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_START, CORNER_RADIUS_END)
    }
  }

  override fun onCrossfadeStarted(reverse: Boolean) {
    alpha = 1f

    sourceView.alpha = if (reverse) 0f else 1f
    sourceBlurView.alpha = if (reverse) 0f else 1f
    targetView.alpha = if (reverse) 1f else 0f
    targetBlurView.alpha = if (reverse) 1f else 0f

    radius = if (reverse) CORNER_RADIUS_END else CORNER_RADIUS_START

    callback?.onAnimationStarted()
  }

  override fun onCrossfadeFinished(reverse: Boolean) {
    if (reverse) {
      return
    }

    callback?.onAnimationFinished()
  }

  private inner class NotifyIfReadyDelegate(var value: Boolean) {
    operator fun getValue(storiesSharedElementCrossFaderView: StoriesSharedElementCrossFaderView, property: KProperty<*>): Boolean {
      return value
    }

    operator fun setValue(storiesSharedElementCrossFaderView: StoriesSharedElementCrossFaderView, property: KProperty<*>, b: Boolean) {
      value = b
      notifyIfReady()
    }
  }

  interface Callback {
    fun onReadyToAnimate()
    fun onAnimationStarted()
    fun onAnimationFinished()
  }
}
